package com.ibm.cps.sql;

import java.math.BigInteger;
import java.util.Map;

import org.bson.types.ObjectId;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.ibm.cps.message.MessageFields;
import com.ibm.cps.processors.SimpleInputProcessor;
import com.ibm.cps.processors.ScalarCalculationProcessor;
import com.ibm.cps.sql.expression.BooleanExpression;
import com.ibm.cps.sql.expression.ExpressionNode;
import com.ibm.rules.RuleMode;
import com.ibm.util.JSONUtility;
import com.ibm.util.LocalConfig;
import com.ibm.util.exception.CPSException;

/**
 * Created by telekinesis on 6/7/15.
 */
public class ProcessorMetadataCreator {
	
	private final String tenantId;
	private final String groupId;
	private final String source;

	public ProcessorMetadataCreator(String tenantId, String groupId, String source) {
		this.tenantId = tenantId;
		this.groupId = groupId;
		this.source = source;
	}

	private ObjectNode createBasicNode(String type, String... parentProcessorId) {
		String processorId = createProcessorId();
		ObjectMapper mapper = new ObjectMapper();
		ObjectNode node = mapper.createObjectNode();
		node.put(MessageFields.TENANTID, tenantId);
		node.put(MessageFields.PROCESSORID, processorId);
		node.put(MessageFields.GROUPID, groupId);
		node.put(MessageFields.SOURCE, source);
		if(parentProcessorId.length > 1){
			ArrayNode parentObjects = mapper.createArrayNode();
			for (String parent : parentProcessorId) {
				parentObjects.add(parent);
			}
			node.set(MessageFields.PARENTPROCESSORS, parentObjects);
		}else if(parentProcessorId.length == 1){
			node.put(MessageFields.PARENTPROCESSORS, parentProcessorId[0]);
		}
		node.put(MessageFields.TYPE, type);
		return node;
	}

	private String createProcessorId() {
		String id = new BigInteger(new ObjectId().toByteArray()).toString(16);
		return id;
	}

	public ObjectNode createInputProcessor(String dataSourceName) throws CPSException{
		ObjectNode node = createBasicNode(MessageFields.INPUT_TYPE);
		if (dataSourceName.equals(MessageFields.UNDEFINED_PROCESSOR)) {
			node.put(MessageFields.NAME, LocalConfig.DEFAULT_DATASOURCE);
		}else {
			node.put(MessageFields.NAME, dataSourceName);
		}
		return node;
	}


	public ObjectNode createVectorCalculationNode(String parentProcessorId, String operatorString, ExpressionNode leftOperand, ExpressionNode rightOperand, String outputFieldName) throws CPSException {
		ObjectNode vectorCalculationNode = createBasicNode(MessageFields.VECTOR_CALCULATION, parentProcessorId);
		vectorCalculationNode.put("operatorString", operatorString);
		vectorCalculationNode.put("leftOperandClass", leftOperand.getClass().getName());
		vectorCalculationNode.put("leftOperandString", JSONUtility.toJsonStringByJacksonAPI(leftOperand));
		vectorCalculationNode.put("rightOperandClass", rightOperand.getClass().getName());
		vectorCalculationNode.put("rightOperandString", JSONUtility.toJsonStringByJacksonAPI(rightOperand));
		vectorCalculationNode.put("outputFieldName", outputFieldName);
		return vectorCalculationNode;
	}

	public ObjectNode createScalarCalculationNode(Map<String, ExpressionNode> outputFields,
			String parentProcessorId) throws CPSException {
		ObjectNode node = createBasicNode(MessageFields.SCALAR_CALCULATION, parentProcessorId);
		ObjectNode fieldTypes = node.putObject("outputFieldsTypeJson");
		ObjectNode fields = node.putObject("outputFieldsJson");
		for (String field : outputFields.keySet()) {
			ExpressionNode expression = outputFields.get(field);
			String expressionType = expression.getClass().getName();
			String expressionJson = JSONUtility.toJsonStringByJacksonAPI(expression);
			fieldTypes.put(field, expressionType);
			fields.put(field, expressionJson);
		}
		return node;
	}

	public ObjectNode createProjectionWithoutCalculation(Map<String, String> fieldMapping,
			String parentProcessorId) {
		ObjectNode projectionNode = createBasicNode(MessageFields.VECTOR_PROJECTION, parentProcessorId);
		ObjectNode outputFields = projectionNode.putObject("fieldMappingJSON");
		for (String inputFieldName : fieldMapping.keySet()) {
			String outputFieldName = fieldMapping.get(inputFieldName);
			outputFields.put(inputFieldName, outputFieldName);
		}
		return projectionNode;
	}

	public ObjectNode createNaiveRuleNode(String parentProcessorId, boolean isEvent, BooleanExpression rule, String ruleName) throws CPSException{
		ObjectNode node = createBasicNode(MessageFields.NAIVE_RULE, parentProcessorId);
		String expressionType = rule.getClass().getName();
		node.put("expressionNodeClassName", expressionType);
		node.put("expressionJson", JSONUtility.toJsonStringByJacksonAPI(rule));
		node.put("ruleName", ruleName);
		if (isEvent) {
			node.put("ruleModeCode", RuleMode.EMIT_WHEN_STATE_CHANGE.toString());
		} else {
			node.put("ruleModeCode", RuleMode.EMIT_WHEN_HIT.toString());
		}
		return node;
	}

	// {"processorid":"0000000000ceab2e30f3154d","rulename":"user given name
	// ","rule":"phase>300"
	// ,"parentprocessors":["0000000000ed66138a01299f"],"type":"rule","model":false
	// ,"topicname":"output topic name","userid":"123456789012345678901239"}
	public ObjectNode createOptimizedRuleNode(String processorId, String parentProcessorId,
			boolean isEvent, String ruleName, String rule, String groupId) {
		ObjectNode ruleNode = createBasicNode(MessageFields.RULE, parentProcessorId);
		ruleNode.put(MessageFields.RULENAME, ruleName + "_rule");
		ruleNode.put(MessageFields.RULE, rule);

		if (isEvent) {
			ruleNode.put(MessageFields.MODEL, 3);
		} else {
			ruleNode.put(MessageFields.MODEL, 0);
		}
		return ruleNode;
	}

	// {"processorid":"00000000014ddeaee0eb2215","windowname":"tight"
	// ,"size":10, "step":1
	// ,"parentprocessors":["0000000000e8151c09017eae"],"type":"TIME_WINDOW"
	// ,"userid":"123456789012345678901239"}
	public ObjectNode createTimeWindowNode(String parentProcessorId, long step, long size){
		ObjectNode node = createBasicNode(MessageFields.SLIDING_WINDOW_TYPE, parentProcessorId);
		node.put("size", size);
		node.put("step", step);
		return node;
	}

	// {"processorid":"00000000014ddeaee0eb2215","windowname":"tight"
		// ,"size":10, "step":1
		// ,"parentprocessors":["0000000000e8151c09017eae"],"type":"COUNT_WINDOW"
		// ,"userid":"123456789012345678901239"}
	public ObjectNode createCountWindowNode(String parentProcessorId, long step, long size){
		ObjectNode node = createBasicNode(MessageFields.COUNT_WINDOW_TYPE, parentProcessorId);
		node.put("step", step);
		node.put("count", size);
		return node;
	}

	public ObjectNode createPublishNode(String outputName,
			String... parents) {
		ObjectNode node = createBasicNode(MessageFields.PUBLISH_TYPE, parents);
		if (outputName.equals(MessageFields.UNDEFINED_PROCESSOR)) {
			node.put(MessageFields.NAME, removeQuotes(LocalConfig.DEFAULT_PUBLISHER));
		}else{
			node.put(MessageFields.NAME, removeQuotes(outputName));
		};
		return node;
	}

	private static String removeQuotes(String name) {
		if (name.charAt(0) == '\"' && name.charAt(name.length() - 1) == '\"') {
			return name.substring(1, name.length() - 1);
		}
		return name;
	}

}
